// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RemotingUnitTest.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Test.Robotics.Runtime
{
    using System;
    using System.IO;
    using System.Reflection;
    using System.Threading;
    using Microsoft.Robotics.Runtime;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Basic set of unit tests for remoting protocol.
    /// </summary>
    [TestClass]
    public class RemotingUnitTest
    {
        /// <summary>
        /// Simple validation that 
        /// </summary>
        [TestMethod]
        [TestCategory("Functional")]
        [TestCategory("Remoting")]
        public void RemoteAgentTest()
        {
            AgentLocator producerAgentSetLocator = new AgentLocator();
            TimerAgent timerAgent = new TimerAgent("TimerAgent", 15);
            IAgentHost timer = producerAgentSetLocator.Register(timerAgent);
            timer.Activate();
            
            ProxyDefinition timerProxyAgent = ProxyDefinition.CreateNetworkEnabledProxy(
                name: "TimerAgentProxy",
                producer: "TimerAgent",
                address: Environment.MachineName,
                publishContracts: new[] { typeof(TimerMessage) },
                publishNetworkPortNumber: 50001,
                controlContracts: null,
                controlNetworkPortNumber: 0);

            UdpNearProxy proxy = new UdpNearProxy(timerProxyAgent, producerAgentSetLocator);
            producerAgentSetLocator.RegisterProxy(proxy);
            proxy.Activate();

            AgentLocator consumerAgentSetLocator = new AgentLocator();
            ProxyDefinition timerRemoteAgent = ProxyDefinition.CreateRemoteProxy(
                name: "TimerAgent",
                address: Environment.MachineName,
                publishContracts: new[] { typeof(TimerMessage) },
                publishNetworkPortNumber: 50001,
                controlContracts: null,
                controlNetworkPortNumber: 0);

            UdpFarProxy remote = new UdpFarProxy(timerRemoteAgent, consumerAgentSetLocator);
            consumerAgentSetLocator.RegisterProxy(remote);

            MessageCollectorAgent<TimerMessage> subscriberAgent = new MessageCollectorAgent<TimerMessage>("Subscriber", timerRemoteAgent.AsProducer<TimerMessage>(), 1);
            IAgentHost subscriber = consumerAgentSetLocator.Register(subscriberAgent);
            subscriber.Activate();

            // wait for a couple of timer cycles. No exception indicates success.
            if (!subscriberAgent.Wait(1000))
            {
                throw new Exception("No messages received");
            }

            subscriber.Deactivate();
            proxy.Deactivate();
            timer.Deactivate();
        }

        /// <summary>
        /// Validates that the agent proxies correctly expose multiple interfaces 
        /// </summary>
        [TestMethod]
        [TestCategory("Functional")]
        [TestCategory("Remoting")]
        public void RemoteAgentMetadataDiscovery()
        {
            AgentLocator producerAgentSetLocator = new AgentLocator();
            EchoAgent echoAgent = new EchoAgent("EchoAgent");
            IAgentHost echoHost = producerAgentSetLocator.Register(echoAgent);
            echoHost.Activate();
            ProxyDefinition defNear = ProxyDefinition.CreateNetworkEnabledProxy(
                "EchoAgentProxy",
                "EchoAgent", 
                Environment.MachineName,
                new[] { typeof(SimpleAgentMessage<int>), typeof(SimpleAgentMessage<double>) }, 
                50000,
                new[] { typeof(SimpleAgentMessage<int>), typeof(SimpleAgentMessage<double>) }, 
                50001);
            UdpNearProxy nearProxy = new UdpNearProxy(defNear, producerAgentSetLocator);
            producerAgentSetLocator.RegisterProxy(nearProxy);
            nearProxy.Activate();

            AgentLocator consumerAgentSetLocator = new AgentLocator();

            ProxyDefinition defFar = ProxyDefinition.CreateRemoteProxy(
                "EchoAgent",
                Environment.MachineName,
                new[] { typeof(SimpleAgentMessage<int>), typeof(SimpleAgentMessage<double>) },
                50000,
                new[] { typeof(SimpleAgentMessage<int>), typeof(SimpleAgentMessage<double>) },
                50001);
            UdpFarProxy farProxy = new UdpFarProxy(defFar, consumerAgentSetLocator);
            consumerAgentSetLocator.RegisterProxy(farProxy);
            DoubleSubscriptionAgent subscriptionAgent = new DoubleSubscriptionAgent("SubscriptionAgent");
            IAgentHost subscriptionHost = consumerAgentSetLocator.Register(subscriptionAgent);
            subscriptionHost.Activate(); // this should also activate the far proxy, since it subscribes to it

            // post control messages, which in turn will cause the echo agent to publish
            IForwarder<SimpleAgentMessage<int>> intControl = farProxy.GetControlPort<SimpleAgentMessage<int>>();
            IForwarder<SimpleAgentMessage<double>> doubleControl = farProxy.GetControlPort<SimpleAgentMessage<double>>();
            intControl.Post(new SimpleAgentMessage<int>(1, AgentMessage.GetCurrentTime()));
            doubleControl.Post(new SimpleAgentMessage<double>(2, AgentMessage.GetCurrentTime()));

            // check that the messages were delivered to the echo agent and published downstream via the rights methods
            if (!echoAgent.Wait(250))
            {
                throw new Exception("Not all methods were called on echoAgent");
            }

            // check that the messages published by the echo agent made it to the subscription agent via the correct methods
            if (!subscriptionAgent.AllMethodsCalledEvent.WaitOne(250))
            {
                throw new Exception("Not all methods were called on subscriptionAgent");
            }

            // sleep a bit to make sure the messages got a chance to be delivered
            subscriptionHost.Deactivate();
            nearProxy.Deactivate();
            echoHost.Deactivate();
        }
    }
}
